package com.hdu.redisHashOperator.service;

import com.hdu.redisHashOperator.annotation.Identity;
import com.hdu.redisHashOperator.annotation.KeyPrefix;
import com.hdu.redisHashOperator.annotation.StoreFiled;
import lombok.extern.slf4j.Slf4j;
import lombok.val;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;


import static com.alibaba.fastjson.JSON.parseObject;
import static com.alibaba.fastjson.JSON.toJSONString;
import static com.hdu.redisHashOperator.util.JedisUtil.getJedis;
import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;


@Slf4j
public class HashCommandOperator {

    private static final String SUCCESS = "OK";
    private static final Long SUCCESS_CODE = 1L;
    private static final String EMPTY_STR = "HashCommandOperator_empty_str";


    public static boolean upsert(Object obj) {
        val redisKey = getRedisKey(obj);
        val storeValues = getStoreValues(obj);
        val result = getJedis().hmset(redisKey, storeValues);
        return SUCCESS.equals(result);
    }

    public static boolean delete(Class<?> clazz, Object id) {
        val keyPrefix = getKeyPrefix(clazz);
        val redisKey = keyPrefix + ":" + id;
        return Objects.equals(getJedis().del(redisKey), SUCCESS_CODE);
    }

    @SuppressWarnings("all")
    public static <T> T query(Class<T> clazz, Object id) {
        val keyPrefix = getKeyPrefix(clazz);
        val redisKey = keyPrefix + ":" + id;
        val storeValueMap = getJedis().hgetAll(redisKey);
        val filedName2Field = getStoreFields(clazz).stream()
                .collect(toMap(
                        field -> getFiledName(field),
                        Function.identity()
                ));
        try {
            final T o = clazz.newInstance();
            storeValueMap.forEach((k, v) -> {
                ofNullable(filedName2Field.get(k))
                        .ifPresent(f -> {
                            f.setAccessible(true);
                            try {
                                f.set(o, parseObject(v, f.getType()));
                            } catch (IllegalAccessException e) {
                                e.printStackTrace();
                            }
                        });
            });
            return (T) o;
        } catch (InstantiationException | IllegalAccessException e) {
            log.error("query error, queryKey : {}", redisKey, e);
            return null;
        }
    }

    @SuppressWarnings("all")
    private static Map<String, String> getStoreValues(Object obj) {
        val dataValues = getStoreFields(obj.getClass()).stream()
                .collect(
                        toMap(
                                field -> getFiledName(field),
                                field -> {
                                    field.setAccessible(true);
                                    try {
                                        return ofNullable(field.get(obj))
                                                .map(fV -> {
                                                    return toJSONString(fV);
                                                })
                                                .orElse(EMPTY_STR);
                                    } catch (IllegalAccessException e) {
                                        e.printStackTrace();
                                    }
                                    return EMPTY_STR;
                                }
                        )
                );
        // 过滤到值为空的字段
        return dataValues.entrySet().stream()
                .filter(entry -> !EMPTY_STR.equals(entry.getValue()))
                .collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
    }


    private static String getIdentityValue(Object t) {
        val identityFields = Arrays.stream(t.getClass().getDeclaredFields())
                .filter(field -> field.isAnnotationPresent(Identity.class))
                .collect(toList());
        if (identityFields.size() != 1) {
            throw new IllegalArgumentException("identity field must be one");
        }
        identityFields.get(0).setAccessible(true);
        try {
            return identityFields.get(0).get(t).toString();
        } catch (IllegalAccessException e) {
            log.error("getIdentityValue error, obj : {}", t, e);
            throw new RuntimeException(e);
        }
    }

    private static String getRedisKey(Object t) {
        return getKeyPrefix(t.getClass()) + ":" + getIdentityValue(t);
    }

    private static String getKeyPrefix(Class<?> clazz) {
        return ofNullable(clazz.getAnnotation(KeyPrefix.class))
                .map(KeyPrefix::value)
                .orElseGet(clazz::getSimpleName);
    }

    private static List<Field> getStoreFields(Class<?> clazz) {
        return Arrays.stream(clazz.getDeclaredFields())
                .filter(field -> {
                    // 排除掉带有 StoreFiled 注解的 并且 StoreFiled.ignore == true
                    return !(
                            field.isAnnotationPresent(StoreFiled.class) &&
                                    field.getAnnotation(StoreFiled.class).ignore()
                    );
                })
                .collect(toList());
    }

    private static String getFiledName(Field field) {
        return ofNullable(field.getAnnotation(StoreFiled.class))
                .map(StoreFiled::value)
                .orElseGet(field::getName);
    }
}
